--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 159b2358f1913abb25eafcc3e23b1777f2ed9f15
Parents : 88193cc
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-06-14T16:35:29-05:00
feat(codebase): windows 7 support
Changes
135 files changed, 1079 insertions(+), 30 deletions(-)
Diff
diff --git a/Taskfile.yml b/Taskfile.yml
index 8c4c5715..599ff5f7 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -331,6 +331,12 @@ tasks:
- "PLATFORM=win32 {{.NPM}} run build-backend"
- "{{.NPM}} run dist -- --win portable"
+ build:legacy:win7-backend:
+ desc: Experimental Windows 7 backend-only build (Python 3.8, no Electron)
+ deps: [build:fe]
+ cmds:
+ - bash scripts/legacy/build-win7-backend-docker.sh
+
dist:win:arm64:
desc: Build Windows arm64 portable EXE
deps: [build:fe]
diff --git a/cx_setup.py b/cx_setup.py
index a70449ee..69bd9312 100644
--- a/cx_setup.py
+++ b/cx_setup.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: 0BSD
import os
+import site
import sys
from pathlib import Path
@@ -51,7 +52,6 @@ packages = [
"RNS",
"RNS.Interfaces",
"LXMF",
- "LXST",
"lxmfy",
"websockets",
"pycparser",
@@ -59,9 +59,56 @@ packages = [
"ply",
]
+if sys.version_info >= (3, 9):
+ packages.extend(["LXST"])
+
if sys.version_info >= (3, 13):
packages.append("audioop")
+runtime_packages = [
+ "aiohttp",
+ "aiohttp_session",
+ "async_timeout",
+ "attrs",
+ "bcrypt",
+ "cbor2",
+ "charset_normalizer",
+ "cryptography",
+ "frozenlist",
+ "idna",
+ "multidict",
+ "psutil",
+ "serial",
+ "tomli",
+ "yarl",
+]
+
+for pkg in runtime_packages:
+ if pkg not in packages:
+ packages.append(pkg)
+
+
+def _collect_mypyc_companion_modules():
+ """cx_Freeze misses charset_normalizer's versioned mypyc .pyd at site-packages root."""
+ includes = []
+ bin_includes = []
+ seen = set()
+ for site_dir in site.getsitepackages():
+ root = Path(site_dir)
+ if not root.is_dir():
+ continue
+ for pyd in root.glob("*__mypyc*.pyd"):
+ module_name = pyd.name.split(".", 1)[0]
+ if module_name in seen:
+ continue
+ seen.add(module_name)
+ includes.append(module_name)
+ bin_includes.append(str(pyd))
+ return includes, bin_includes
+
+
+mypyc_includes, mypyc_bin_includes = _collect_mypyc_companion_modules()
+
setup(
name="ReticulumMeshChatX",
version=__version__,
@@ -79,6 +126,8 @@ setup(
options={
"build_exe": {
"packages": packages,
+ "includes": mypyc_includes,
+ "bin_includes": mypyc_bin_includes,
"include_files": include_files,
"excludes": [
"PIL",
diff --git a/meshchatx/__init__.py b/meshchatx/__init__.py
index d37e8d09..ef7c1558 100644
--- a/meshchatx/__init__.py
+++ b/meshchatx/__init__.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Reticulum MeshChatX - A mesh network communications app."""
# Synced from package.json via scripts/sync_version.js (also writes meshchatx/src/version.py).
diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index 18720321..c3a6fe2c 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -1,6 +1,19 @@
#!/usr/bin/env python
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
+import sys
+
+from meshchatx.src.compat_py38 import removeprefix, removesuffix
+from meshchatx.src.lxst_optional import ensure_lxst, lxst_available
+
+if sys.version_info < (3, 9):
+ from meshchatx.src.compat_py38 import bootstrap
+
+ bootstrap()
+
+ensure_lxst()
+
import argparse
import asyncio
import atexit
@@ -8142,8 +8155,10 @@ class ReticulumMeshChat:
# serve telephone status
@routes.get("/api/v1/telephone/status")
async def telephone_status(request):
- # make sure telephone is enabled
- if self.telephone_manager.telephone is None:
+ if (
+ not self.telephone_manager
+ or self.telephone_manager.telephone is None
+ ):
return web.json_response(
{
"enabled": False,
@@ -12829,7 +12844,7 @@ class ReticulumMeshChat:
x = int(request.match_info.get("x"))
y_str = request.match_info.get("y")
# remove .png if present
- y_str = y_str.removesuffix(".png")
+ y_str = removesuffix(y_str, ".png")
y = int(y_str)
tile_data = self.map_manager.get_tile(z, x, y)
@@ -16080,14 +16095,17 @@ class ReticulumMeshChat:
ctx = context or self.current_context
if not ctx:
return {}
+ telephone = ctx.telephone_manager.telephone
+ telephone_destination = getattr(telephone, "destination", None)
return {
"display_name": ctx.config.display_name.get(),
"identity_hash": ctx.identity.hash.hex(),
"identity_public_key": ctx.identity.get_public_key().hex(),
"lxmf_address_hash": ctx.local_lxmf_destination.hexhash,
- "telephone_address_hash": ctx.telephone_manager.telephone.destination.hexhash
- if ctx.telephone_manager.telephone
+ "telephone_address_hash": getattr(telephone_destination, "hexhash", None)
+ if telephone_destination is not None
else None,
+ "lxst_available": lxst_available(),
"is_transport_enabled": (
self.reticulum.transport_enabled()
if hasattr(self, "reticulum") and self.reticulum
@@ -16150,7 +16168,8 @@ class ReticulumMeshChat:
"map_tile_server_url": ctx.config.map_tile_server_url.get(),
"map_nominatim_api_url": ctx.config.map_nominatim_api_url.get(),
"do_not_disturb_enabled": ctx.config.do_not_disturb_enabled.get(),
- "telephone_enabled": ctx.config.telephone_enabled.get(),
+ "telephone_enabled": ctx.config.telephone_enabled.get()
+ and lxst_available(),
"telephone_allow_calls_from_contacts_only": ctx.config.telephone_allow_calls_from_contacts_only.get(),
"telephone_announce_enabled": ctx.config.telephone_announce_enabled.get(),
"telephone_audio_profile_id": ctx.config.telephone_audio_profile_id.get(),
@@ -18681,7 +18700,7 @@ class ReticulumMeshChat:
continue
if node.destination.hash == destination_hash:
page_name = page_path.lstrip("/")
- page_name = page_name.removeprefix("page/")
+ page_name = removeprefix(page_name, "page/")
page_name = os.path.basename(page_name)
content = node.get_page_content(page_name)
if content is not None:
@@ -18699,7 +18718,7 @@ class ReticulumMeshChat:
continue
if node.destination.hash == destination_hash:
file_name = file_path.lstrip("/")
- file_name = file_name.removeprefix("file/")
+ file_name = removeprefix(file_name, "file/")
file_name = os.path.basename(file_name)
if not file_name or file_name in (".", ".."):
return None
diff --git a/meshchatx/src/__init__.py b/meshchatx/src/__init__.py
index 2ee29f9e..d54443a5 100644
--- a/meshchatx/src/__init__.py
+++ b/meshchatx/src/__init__.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: MIT
+from __future__ import annotations
import sys
# NOTE: this class is required to be able to use print/log commands and have them flush to stdout and stderr immediately
diff --git a/meshchatx/src/backend/__init__.py b/meshchatx/src/backend/__init__.py
index 16982566..7cb94f91 100644
--- a/meshchatx/src/backend/__init__.py
+++ b/meshchatx/src/backend/__init__.py
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Backend utilities shared by the Reticulum MeshChatX CLI."""
diff --git a/meshchatx/src/backend/announce_handler.py b/meshchatx/src/backend/announce_handler.py
index 4d312cbd..79556664 100644
--- a/meshchatx/src/backend/announce_handler.py
+++ b/meshchatx/src/backend/announce_handler.py
@@ -2,6 +2,7 @@
# an announce handler that forwards announces to a provided callback for the provided aspect filter
# this handler exists so we can have access to the original aspect, as this is not provided in the announce itself
+from __future__ import annotations
class AnnounceHandler:
def __init__(self, aspect_filter: str, received_announce_callback):
self.aspect_filter = aspect_filter
diff --git a/meshchatx/src/backend/announce_manager.py b/meshchatx/src/backend/announce_manager.py
index 21bdda57..7cb0954c 100644
--- a/meshchatx/src/backend/announce_manager.py
+++ b/meshchatx/src/backend/announce_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
from .database import Database
diff --git a/meshchatx/src/backend/archiver_manager.py b/meshchatx/src/backend/archiver_manager.py
index 6ba48945..84744058 100644
--- a/meshchatx/src/backend/archiver_manager.py
+++ b/meshchatx/src/backend/archiver_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import hashlib
from .database import Database
diff --git a/meshchatx/src/backend/async_utils.py b/meshchatx/src/backend/async_utils.py
index 40205b1f..ae49a1aa 100644
--- a/meshchatx/src/backend/async_utils.py
+++ b/meshchatx/src/backend/async_utils.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
import asyncio
import threading
from collections.abc import Coroutine
diff --git a/meshchatx/src/backend/auto_propagation_manager.py b/meshchatx/src/backend/auto_propagation_manager.py
index f1c02c36..4dc3570a 100644
--- a/meshchatx/src/backend/auto_propagation_manager.py
+++ b/meshchatx/src/backend/auto_propagation_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import contextlib
import time
diff --git a/meshchatx/src/backend/bot_handler.py b/meshchatx/src/backend/bot_handler.py
index 87ae310e..25e2ef75 100644
--- a/meshchatx/src/backend/bot_handler.py
+++ b/meshchatx/src/backend/bot_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import contextlib
import json
import logging
diff --git a/meshchatx/src/backend/bot_process.py b/meshchatx/src/backend/bot_process.py
index 2414b321..f0a1d75c 100644
--- a/meshchatx/src/backend/bot_process.py
+++ b/meshchatx/src/backend/bot_process.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import argparse
import contextlib
import os
diff --git a/meshchatx/src/backend/bot_templates.py b/meshchatx/src/backend/bot_templates.py
index be6d0897..da9fb6e8 100644
--- a/meshchatx/src/backend/bot_templates.py
+++ b/meshchatx/src/backend/bot_templates.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import re
import time
from datetime import UTC, datetime, timedelta
diff --git a/meshchatx/src/backend/colour_utils.py b/meshchatx/src/backend/colour_utils.py
index 5d49401e..24c415a7 100644
--- a/meshchatx/src/backend/colour_utils.py
+++ b/meshchatx/src/backend/colour_utils.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
class ColourUtils:
@staticmethod
def hex_colour_to_byte_array(hex_colour):
diff --git a/meshchatx/src/backend/community_interfaces.py b/meshchatx/src/backend/community_interfaces.py
index 392668f0..c61ce66c 100644
--- a/meshchatx/src/backend/community_interfaces.py
+++ b/meshchatx/src/backend/community_interfaces.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
diff --git a/meshchatx/src/backend/config_manager.py b/meshchatx/src/backend/config_manager.py
index c4fcd96b..c1d06b1a 100644
--- a/meshchatx/src/backend/config_manager.py
+++ b/meshchatx/src/backend/config_manager.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
class ConfigManager:
def __init__(self, db):
self.db = db
diff --git a/meshchatx/src/backend/database/__init__.py b/meshchatx/src/backend/database/__init__.py
index 9cbe20d1..6933c2d9 100644
--- a/meshchatx/src/backend/database/__init__.py
+++ b/meshchatx/src/backend/database/__init__.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import json
import logging
import os
diff --git a/meshchatx/src/backend/database/announces.py b/meshchatx/src/backend/database/announces.py
index 289cc861..f3ef5e11 100644
--- a/meshchatx/src/backend/database/announces.py
+++ b/meshchatx/src/backend/database/announces.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from datetime import UTC, datetime
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/config.py b/meshchatx/src/backend/database/config.py
index 5ea36dcd..f50f49a3 100644
--- a/meshchatx/src/backend/database/config.py
+++ b/meshchatx/src/backend/database/config.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from datetime import UTC, datetime
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/contacts.py b/meshchatx/src/backend/database/contacts.py
index 171b1383..92922e71 100644
--- a/meshchatx/src/backend/database/contacts.py
+++ b/meshchatx/src/backend/database/contacts.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/crash_history.py b/meshchatx/src/backend/database/crash_history.py
index 703130df..ec240a27 100644
--- a/meshchatx/src/backend/database/crash_history.py
+++ b/meshchatx/src/backend/database/crash_history.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import json
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/debug_logs.py b/meshchatx/src/backend/database/debug_logs.py
index 39c37923..22130d96 100644
--- a/meshchatx/src/backend/database/debug_logs.py
+++ b/meshchatx/src/backend/database/debug_logs.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from datetime import UTC, datetime
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/gifs.py b/meshchatx/src/backend/database/gifs.py
index 4c91ae56..c37588c2 100644
--- a/meshchatx/src/backend/database/gifs.py
+++ b/meshchatx/src/backend/database/gifs.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
import sqlite3
import time
diff --git a/meshchatx/src/backend/database/map_drawings.py b/meshchatx/src/backend/database/map_drawings.py
index d3593b23..17253132 100644
--- a/meshchatx/src/backend/database/map_drawings.py
+++ b/meshchatx/src/backend/database/map_drawings.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from datetime import UTC, datetime
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/messages.py b/meshchatx/src/backend/database/messages.py
index 5f456cda..d4df65c6 100644
--- a/meshchatx/src/backend/database/messages.py
+++ b/meshchatx/src/backend/database/messages.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import json
from datetime import UTC, datetime
diff --git a/meshchatx/src/backend/database/misc.py b/meshchatx/src/backend/database/misc.py
index eb28ec04..fbf22248 100644
--- a/meshchatx/src/backend/database/misc.py
+++ b/meshchatx/src/backend/database/misc.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from datetime import UTC, datetime
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/provider.py b/meshchatx/src/backend/database/provider.py
index 3f686e40..869e5ae5 100644
--- a/meshchatx/src/backend/database/provider.py
+++ b/meshchatx/src/backend/database/provider.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import sqlite3
import sys
import threading
diff --git a/meshchatx/src/backend/database/ringtones.py b/meshchatx/src/backend/database/ringtones.py
index aa540ae8..ba32d37f 100644
--- a/meshchatx/src/backend/database/ringtones.py
+++ b/meshchatx/src/backend/database/ringtones.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from datetime import UTC, datetime
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/schema.py b/meshchatx/src/backend/database/schema.py
index 2d4033fe..3dad4667 100644
--- a/meshchatx/src/backend/database/schema.py
+++ b/meshchatx/src/backend/database/schema.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import re
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/stickers.py b/meshchatx/src/backend/database/stickers.py
index 15c641ab..19af22ed 100644
--- a/meshchatx/src/backend/database/stickers.py
+++ b/meshchatx/src/backend/database/stickers.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
import sqlite3
import time
diff --git a/meshchatx/src/backend/database/telemetry.py b/meshchatx/src/backend/database/telemetry.py
index dc7d23da..a7ebe9ad 100644
--- a/meshchatx/src/backend/database/telemetry.py
+++ b/meshchatx/src/backend/database/telemetry.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import json
from datetime import UTC, datetime
diff --git a/meshchatx/src/backend/database/telephone.py b/meshchatx/src/backend/database/telephone.py
index a45f5689..2750ca3a 100644
--- a/meshchatx/src/backend/database/telephone.py
+++ b/meshchatx/src/backend/database/telephone.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/database/voicemails.py b/meshchatx/src/backend/database/voicemails.py
index c062aa34..5ef921e0 100644
--- a/meshchatx/src/backend/database/voicemails.py
+++ b/meshchatx/src/backend/database/voicemails.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from .provider import DatabaseProvider
diff --git a/meshchatx/src/backend/diagnostics/__init__.py b/meshchatx/src/backend/diagnostics/__init__.py
index ff8b1dfd..ed6566f5 100644
--- a/meshchatx/src/backend/diagnostics/__init__.py
+++ b/meshchatx/src/backend/diagnostics/__init__.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from .memory_diagnostics import MemoryDiagnostics, get_diagnostics, take_heap_snapshot
__all__ = ["MemoryDiagnostics", "get_diagnostics", "take_heap_snapshot"]
diff --git a/meshchatx/src/backend/diagnostics/memory_diagnostics.py b/meshchatx/src/backend/diagnostics/memory_diagnostics.py
index bcba72e5..857caee2 100644
--- a/meshchatx/src/backend/diagnostics/memory_diagnostics.py
+++ b/meshchatx/src/backend/diagnostics/memory_diagnostics.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
import collections
import gc
import sys
diff --git a/meshchatx/src/backend/docs_manager.py b/meshchatx/src/backend/docs_manager.py
index 52963298..796953c1 100644
--- a/meshchatx/src/backend/docs_manager.py
+++ b/meshchatx/src/backend/docs_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import html
import io
import logging
diff --git a/meshchatx/src/backend/forwarding_manager.py b/meshchatx/src/backend/forwarding_manager.py
index 0577363c..aafe8ae2 100644
--- a/meshchatx/src/backend/forwarding_manager.py
+++ b/meshchatx/src/backend/forwarding_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
import contextlib
import os
diff --git a/meshchatx/src/backend/gif_utils.py b/meshchatx/src/backend/gif_utils.py
index 34c65c47..da31eb97 100644
--- a/meshchatx/src/backend/gif_utils.py
+++ b/meshchatx/src/backend/gif_utils.py
@@ -16,6 +16,8 @@ from __future__ import annotations
import base64
import hashlib
+from meshchatx.src.compat_py38 import removeprefix
+
MAX_GIF_BYTES = 5 * 1024 * 1024
MAX_GIFS_PER_IDENTITY = 1000
@@ -31,7 +33,7 @@ def normalize_image_type(image_type: str | None) -> str | None:
if not image_type:
return None
t = str(image_type).strip().lower()
- t = t.removeprefix("image/")
+ t = removeprefix(t, "image/")
t = _TYPE_ALIASES.get(t, t)
return t if t in _ALLOWED_TYPES else None
diff --git a/meshchatx/src/backend/identity_context.py b/meshchatx/src/backend/identity_context.py
index 94207623..7f71666d 100644
--- a/meshchatx/src/backend/identity_context.py
+++ b/meshchatx/src/backend/identity_context.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import contextlib
import os
diff --git a/meshchatx/src/backend/identity_manager.py b/meshchatx/src/backend/identity_manager.py
index 78a736e5..5a779f33 100644
--- a/meshchatx/src/backend/identity_manager.py
+++ b/meshchatx/src/backend/identity_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
import contextlib
import json
diff --git a/meshchatx/src/backend/integrity_manager.py b/meshchatx/src/backend/integrity_manager.py
index 7185c831..db208d1f 100644
--- a/meshchatx/src/backend/integrity_manager.py
+++ b/meshchatx/src/backend/integrity_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import fnmatch
import hashlib
import json
diff --git a/meshchatx/src/backend/interface_config_parser.py b/meshchatx/src/backend/interface_config_parser.py
index e66cb597..d2c34085 100644
--- a/meshchatx/src/backend/interface_config_parser.py
+++ b/meshchatx/src/backend/interface_config_parser.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
import RNS.vendor.configobj
diff --git a/meshchatx/src/backend/interface_editor.py b/meshchatx/src/backend/interface_editor.py
index a43d51e2..0021d5fc 100644
--- a/meshchatx/src/backend/interface_editor.py
+++ b/meshchatx/src/backend/interface_editor.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
import re
import RNS
diff --git a/meshchatx/src/backend/interfaces/WebsocketClientInterface.py b/meshchatx/src/backend/interfaces/WebsocketClientInterface.py
index 14c36f1b..9cd24fb9 100644
--- a/meshchatx/src/backend/interfaces/WebsocketClientInterface.py
+++ b/meshchatx/src/backend/interfaces/WebsocketClientInterface.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
import threading
import time
diff --git a/meshchatx/src/backend/interfaces/WebsocketServerInterface.py b/meshchatx/src/backend/interfaces/WebsocketServerInterface.py
index 2b7a548e..189b771e 100644
--- a/meshchatx/src/backend/interfaces/WebsocketServerInterface.py
+++ b/meshchatx/src/backend/interfaces/WebsocketServerInterface.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD AND MIT
+from __future__ import annotations
import threading
import time
diff --git a/meshchatx/src/backend/interfaces/__init__.py b/meshchatx/src/backend/interfaces/__init__.py
index a2ccb49c..f505d8d6 100644
--- a/meshchatx/src/backend/interfaces/__init__.py
+++ b/meshchatx/src/backend/interfaces/__init__.py
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Shared transport interfaces for MeshChatX."""
diff --git a/meshchatx/src/backend/legacy_migrator.py b/meshchatx/src/backend/legacy_migrator.py
index 6247b5ec..8903fe13 100644
--- a/meshchatx/src/backend/legacy_migrator.py
+++ b/meshchatx/src/backend/legacy_migrator.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import logging
import os
import shutil
diff --git a/meshchatx/src/backend/local_message_retention.py b/meshchatx/src/backend/local_message_retention.py
index 6670fb8d..431620f8 100644
--- a/meshchatx/src/backend/local_message_retention.py
+++ b/meshchatx/src/backend/local_message_retention.py
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Local-only age-based deletion of stored LXMF rows (this device, no network signal)."""
import logging
diff --git a/meshchatx/src/backend/lxmf_message_fields.py b/meshchatx/src/backend/lxmf_message_fields.py
index 7a5f485a..695596f3 100644
--- a/meshchatx/src/backend/lxmf_message_fields.py
+++ b/meshchatx/src/backend/lxmf_message_fields.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: MIT
# helper class for passing around an lxmf audio field
+from __future__ import annotations
class LxmfAudioField:
def __init__(self, audio_mode: int, audio_bytes: bytes):
self.audio_mode = audio_mode
diff --git a/meshchatx/src/backend/lxmf_utils.py b/meshchatx/src/backend/lxmf_utils.py
index 598e7f51..7ba442e8 100644
--- a/meshchatx/src/backend/lxmf_utils.py
+++ b/meshchatx/src/backend/lxmf_utils.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
import json
diff --git a/meshchatx/src/backend/map_manager.py b/meshchatx/src/backend/map_manager.py
index 4ce26502..4bf341ae 100644
--- a/meshchatx/src/backend/map_manager.py
+++ b/meshchatx/src/backend/map_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import base64
import math
diff --git a/meshchatx/src/backend/markdown_renderer.py b/meshchatx/src/backend/markdown_renderer.py
index 5e792e8a..83812c16 100644
--- a/meshchatx/src/backend/markdown_renderer.py
+++ b/meshchatx/src/backend/markdown_renderer.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import html
import re
diff --git a/meshchatx/src/backend/mdi_icon_util.py b/meshchatx/src/backend/mdi_icon_util.py
index 945cf92e..ab78fc98 100644
--- a/meshchatx/src/backend/mdi_icon_util.py
+++ b/meshchatx/src/backend/mdi_icon_util.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Validation for Material Design Icons names (kebab-case, LXMF-style)."""
import re
diff --git a/meshchatx/src/backend/meshchat_utils.py b/meshchatx/src/backend/meshchat_utils.py
index de00b771..8dd8df08 100644
--- a/meshchatx/src/backend/meshchat_utils.py
+++ b/meshchatx/src/backend/meshchat_utils.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import base64
import contextlib
import json
diff --git a/meshchatx/src/backend/message_handler.py b/meshchatx/src/backend/message_handler.py
index 10072df2..0491c189 100644
--- a/meshchatx/src/backend/message_handler.py
+++ b/meshchatx/src/backend/message_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from .database import Database
diff --git a/meshchatx/src/backend/nomadnet_downloader.py b/meshchatx/src/backend/nomadnet_downloader.py
index 8b9ff6d1..d1c80247 100644
--- a/meshchatx/src/backend/nomadnet_downloader.py
+++ b/meshchatx/src/backend/nomadnet_downloader.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import io
import os
diff --git a/meshchatx/src/backend/nomadnet_utils.py b/meshchatx/src/backend/nomadnet_utils.py
index cf074195..79f5c915 100644
--- a/meshchatx/src/backend/nomadnet_utils.py
+++ b/meshchatx/src/backend/nomadnet_utils.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
def convert_nomadnet_string_data_to_map(path_data: str | None):
data = {}
if path_data is not None:
diff --git a/meshchatx/src/backend/page_node.py b/meshchatx/src/backend/page_node.py
index 2e6ff773..455471d4 100644
--- a/meshchatx/src/backend/page_node.py
+++ b/meshchatx/src/backend/page_node.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""PageNode: Serves Micron pages and files over RNS.
Each PageNode owns an RNS Destination (SINGLE, IN) with the aspect
diff --git a/meshchatx/src/backend/page_node_manager.py b/meshchatx/src/backend/page_node_manager.py
index c56bb4c8..0547251a 100644
--- a/meshchatx/src/backend/page_node_manager.py
+++ b/meshchatx/src/backend/page_node_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""PageNodeManager: Manages the lifecycle of multiple PageNode instances.
Handles creation, deletion, persistence, start/stop, and announce
diff --git a/meshchatx/src/backend/persistent_log_handler.py b/meshchatx/src/backend/persistent_log_handler.py
index ede1bc5a..b75e8469 100644
--- a/meshchatx/src/backend/persistent_log_handler.py
+++ b/meshchatx/src/backend/persistent_log_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import collections
import logging
import math
diff --git a/meshchatx/src/backend/recovery/__init__.py b/meshchatx/src/backend/recovery/__init__.py
index 1ac18bab..a0dd6b17 100644
--- a/meshchatx/src/backend/recovery/__init__.py
+++ b/meshchatx/src/backend/recovery/__init__.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
from .crash_recovery import CrashRecovery
from .health_monitor import HealthMonitor
from .memory_preflight import (
diff --git a/meshchatx/src/backend/recovery/crash_recovery.py b/meshchatx/src/backend/recovery/crash_recovery.py
index a60ae328..57546d94 100644
--- a/meshchatx/src/backend/recovery/crash_recovery.py
+++ b/meshchatx/src/backend/recovery/crash_recovery.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Crash recovery and adaptive diagnostics for MeshChatX.
Uses entropy, KL-divergence, and Bayesian weight learning. Crash history is
@@ -638,7 +639,7 @@ class CrashRecovery:
entropy = sum(h(q) for q in q_vec)
# Systemic Divergence: How 'surprising' this state is compared to ideal
- divergence = sum(kl_div(q, p) for q, p in zip(q_vec, p_vec, strict=False))
+ divergence = sum(kl_div(q, p) for q, p in zip(q_vec, p_vec))
return entropy, divergence
diff --git a/meshchatx/src/backend/recovery/health_monitor.py b/meshchatx/src/backend/recovery/health_monitor.py
index 0b4c5fa0..04fb8399 100644
--- a/meshchatx/src/backend/recovery/health_monitor.py
+++ b/meshchatx/src/backend/recovery/health_monitor.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Lightweight predictive health monitor.
Runs as a daemon thread on a 5-minute interval, reading in-memory
diff --git a/meshchatx/src/backend/reticulum_pathfinding.py b/meshchatx/src/backend/reticulum_pathfinding.py
index e70564c0..aacaf0af 100644
--- a/meshchatx/src/backend/reticulum_pathfinding.py
+++ b/meshchatx/src/backend/reticulum_pathfinding.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import contextlib
import time
diff --git a/meshchatx/src/backend/ringtone_manager.py b/meshchatx/src/backend/ringtone_manager.py
index d7e08f39..21979a87 100644
--- a/meshchatx/src/backend/ringtone_manager.py
+++ b/meshchatx/src/backend/ringtone_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import os
from .audio_codec import encode_audio_to_ogg_opus
diff --git a/meshchatx/src/backend/rncp_handler.py b/meshchatx/src/backend/rncp_handler.py
index b9c2bbd8..d3de3266 100644
--- a/meshchatx/src/backend/rncp_handler.py
+++ b/meshchatx/src/backend/rncp_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import contextlib
import os
diff --git a/meshchatx/src/backend/rnpath_handler.py b/meshchatx/src/backend/rnpath_handler.py
index 5ce208ee..262441a3 100644
--- a/meshchatx/src/backend/rnpath_handler.py
+++ b/meshchatx/src/backend/rnpath_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import RNS
diff --git a/meshchatx/src/backend/rnpath_trace_handler.py b/meshchatx/src/backend/rnpath_trace_handler.py
index 10955d0b..361e196e 100644
--- a/meshchatx/src/backend/rnpath_trace_handler.py
+++ b/meshchatx/src/backend/rnpath_trace_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import logging
import time
diff --git a/meshchatx/src/backend/rnprobe_handler.py b/meshchatx/src/backend/rnprobe_handler.py
index 53eeaacb..57e6b075 100644
--- a/meshchatx/src/backend/rnprobe_handler.py
+++ b/meshchatx/src/backend/rnprobe_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import logging
import os
diff --git a/meshchatx/src/backend/rnstatus_handler.py b/meshchatx/src/backend/rnstatus_handler.py
index a3efc013..1ce3ff09 100644
--- a/meshchatx/src/backend/rnstatus_handler.py
+++ b/meshchatx/src/backend/rnstatus_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import contextlib
import time
from typing import Any
diff --git a/meshchatx/src/backend/rrc/__init__.py b/meshchatx/src/backend/rrc/__init__.py
index 2bdd9273..c37f0fa1 100644
--- a/meshchatx/src/backend/rrc/__init__.py
+++ b/meshchatx/src/backend/rrc/__init__.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Reticulum Relay Chat (RRC) implementation for MeshChatX.
This package provides a wire-compatible client for RRC hubs (rrcd) and the
diff --git a/meshchatx/src/backend/rrc/hub_commands.py b/meshchatx/src/backend/rrc/hub_commands.py
index 222782c7..a6ce51f9 100644
--- a/meshchatx/src/backend/rrc/hub_commands.py
+++ b/meshchatx/src/backend/rrc/hub_commands.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Operator and moderation commands for hosted RRC hubs (rrcd-compatible)."""
import time
diff --git a/meshchatx/src/backend/rrc/hub_policy.py b/meshchatx/src/backend/rrc/hub_policy.py
index aa5fb2e3..114685d5 100644
--- a/meshchatx/src/backend/rrc/hub_policy.py
+++ b/meshchatx/src/backend/rrc/hub_policy.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Server-wide trusted and banned identity lists for hosted RRC hubs."""
import os
diff --git a/meshchatx/src/backend/rrc/identity_util.py b/meshchatx/src/backend/rrc/identity_util.py
index b15fb12c..1e9c72e9 100644
--- a/meshchatx/src/backend/rrc/identity_util.py
+++ b/meshchatx/src/backend/rrc/identity_util.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Identity hash parsing and session lookup helpers for RRC hub moderation."""
IDENTITY_HASH_LENS = (16, 32)
diff --git a/meshchatx/src/backend/rrc/manager.py b/meshchatx/src/backend/rrc/manager.py
index 3ccbe495..4bd553ad 100644
--- a/meshchatx/src/backend/rrc/manager.py
+++ b/meshchatx/src/backend/rrc/manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Reticulum Relay Chat session management.
Contains :class:`RRCHub`, which owns the Reticulum link and protocol state for a
diff --git a/meshchatx/src/backend/rrc/protocol.py b/meshchatx/src/backend/rrc/protocol.py
index d1d09016..039b402c 100644
--- a/meshchatx/src/backend/rrc/protocol.py
+++ b/meshchatx/src/backend/rrc/protocol.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Wire protocol primitives for Reticulum Relay Chat.
This module mirrors the constants and envelope structure defined by the RRC
diff --git a/meshchatx/src/backend/rrc/room_registry.py b/meshchatx/src/backend/rrc/room_registry.py
index 65348477..14e80d1b 100644
--- a/meshchatx/src/backend/rrc/room_registry.py
+++ b/meshchatx/src/backend/rrc/room_registry.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Room state, modes, and rooms.toml persistence for hosted RRC hubs."""
import time
diff --git a/meshchatx/src/backend/rrc/rooms_toml.py b/meshchatx/src/backend/rrc/rooms_toml.py
index c57262f0..a9b0ce43 100644
--- a/meshchatx/src/backend/rrc/rooms_toml.py
+++ b/meshchatx/src/backend/rrc/rooms_toml.py
@@ -1,11 +1,16 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Load and save rrcd-compatible rooms.toml for hosted RRC hubs."""
import os
import threading
import time
-import tomllib
+
+try:
+ import tomllib
+except ModuleNotFoundError:
+ import tomli as tomllib
from meshchatx.src.backend.rrc.identity_util import parse_identity_hash
diff --git a/meshchatx/src/backend/rrc/server.py b/meshchatx/src/backend/rrc/server.py
index c41db8e2..2e810ce4 100644
--- a/meshchatx/src/backend/rrc/server.py
+++ b/meshchatx/src/backend/rrc/server.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Reticulum Relay Chat hub hosting.
Provides :class:`RRCHubServer`, a self-contained RRC hub that listens on a
diff --git a/meshchatx/src/backend/sideband_commands.py b/meshchatx/src/backend/sideband_commands.py
index eb93367f..9ed75313 100644
--- a/meshchatx/src/backend/sideband_commands.py
+++ b/meshchatx/src/backend/sideband_commands.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: MIT
# https://github.com/markqvist/Sideband/blob/e515889e210037f881c201e0d627a7b09a48eb69/sbapp/sideband/sense.py#L11
+from __future__ import annotations
class SidebandCommands:
TELEMETRY_REQUEST = 0x01
diff --git a/meshchatx/src/backend/sticker_utils.py b/meshchatx/src/backend/sticker_utils.py
index 92b0bd65..576a4c2b 100644
--- a/meshchatx/src/backend/sticker_utils.py
+++ b/meshchatx/src/backend/sticker_utils.py
@@ -35,6 +35,8 @@ import json
import struct
import zlib
+from meshchatx.src.compat_py38 import removeprefix
+
# Telegram-aligned size limits.
MAX_STATIC_BYTES = 512 * 1024
MAX_ANIMATED_BYTES = 64 * 1024
@@ -80,9 +82,9 @@ def normalize_image_type(image_type: str | None) -> str | None:
if not image_type:
return None
t = str(image_type).strip().lower()
- t = t.removeprefix("image/")
- t = t.removeprefix("video/")
- t = t.removeprefix("application/")
+ t = removeprefix(t, "image/")
+ t = removeprefix(t, "video/")
+ t = removeprefix(t, "application/")
t = _TYPE_ALIASES.get(t, t)
return t if t in _ALLOWED_TYPES else None
diff --git a/meshchatx/src/backend/storage_lock.py b/meshchatx/src/backend/storage_lock.py
index 70a891e0..e1782990 100644
--- a/meshchatx/src/backend/storage_lock.py
+++ b/meshchatx/src/backend/storage_lock.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import atexit
import os
import sys
diff --git a/meshchatx/src/backend/telemetry_utils.py b/meshchatx/src/backend/telemetry_utils.py
index a05109ee..04d6a715 100644
--- a/meshchatx/src/backend/telemetry_utils.py
+++ b/meshchatx/src/backend/telemetry_utils.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import struct
import time
diff --git a/meshchatx/src/backend/telephone_manager.py b/meshchatx/src/backend/telephone_manager.py
index dd884042..782a741f 100644
--- a/meshchatx/src/backend/telephone_manager.py
+++ b/meshchatx/src/backend/telephone_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import base64
import contextlib
@@ -10,6 +11,8 @@ import time
import RNS
from LXST import Telephone
+from meshchatx.src.lxst_optional import lxst_available
+
from meshchatx.src.backend import reticulum_pathfinding
from meshchatx.src.backend.meshchat_utils import (
hex_identifier_to_bytes,
@@ -97,6 +100,8 @@ class TelephoneManager:
def init_telephone(self):
if self.telephone is not None:
return
+ if not lxst_available():
+ return
if self.config_manager and not self.config_manager.telephone_enabled.get():
return
diff --git a/meshchatx/src/backend/translator_handler.py b/meshchatx/src/backend/translator_handler.py
index 86cc7a47..c457fa4b 100644
--- a/meshchatx/src/backend/translator_handler.py
+++ b/meshchatx/src/backend/translator_handler.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import concurrent.futures
import os
diff --git a/meshchatx/src/backend/voicemail_manager.py b/meshchatx/src/backend/voicemail_manager.py
index 21c8b9ae..02c39672 100644
--- a/meshchatx/src/backend/voicemail_manager.py
+++ b/meshchatx/src/backend/voicemail_manager.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import contextlib
import os
import platform
diff --git a/meshchatx/src/backend/web_audio_bridge.py b/meshchatx/src/backend/web_audio_bridge.py
index 2425c676..74c66391 100644
--- a/meshchatx/src/backend/web_audio_bridge.py
+++ b/meshchatx/src/backend/web_audio_bridge.py
@@ -1,11 +1,11 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
import asyncio
import contextlib
import json
import threading
-import numpy as np
import RNS
from LXST.Codecs import Null, Raw
from LXST.Mixer import Mixer
@@ -48,6 +48,8 @@ class WebAudioSource(LocalSource):
def push_pcm(self, pcm_bytes: bytes):
try:
+ import numpy as np
+
samples = (
np.frombuffer(pcm_bytes, dtype=np.int16).astype(np.float32) / 32768.0
)
@@ -73,6 +75,8 @@ class WebAudioSink(LocalSink):
def handle_frame(self, frame, source):
try:
+ import numpy as np
+
# frame is expected to be numpy float PCM from receive mixer
if hasattr(frame, "astype"):
samples = np.clip(frame, -1.0, 1.0).astype(np.float32)
diff --git a/meshchatx/src/compat_py38.py b/meshchatx/src/compat_py38.py
new file mode 100644
index 00000000..5400f398
--- /dev/null
+++ b/meshchatx/src/compat_py38.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Runtime shims for Python 3.8 (Windows 7 backend-only legacy builds)."""
+
+from __future__ import annotations
+
+import asyncio
+import functools
+import sys
+
+
+def removeprefix(value: str, prefix: str) -> str:
+ if value.startswith(prefix):
+ return value[len(prefix) :]
+ return value
+
+
+def removesuffix(value: str, suffix: str) -> str:
+ if value.endswith(suffix):
+ return value[: -len(suffix)]
+ return value
+
+
+def _install_asyncio_to_thread() -> None:
+ if hasattr(asyncio, "to_thread"):
+ return
+
+ async def to_thread(func, /, *args, **kwargs):
+ loop = asyncio.get_event_loop()
+ bound = functools.partial(func, *args, **kwargs)
+ return await loop.run_in_executor(None, bound)
+
+ asyncio.to_thread = to_thread
+
+
+def _install_tomllib() -> None:
+ try:
+ import tomllib # noqa: F401
+ except ModuleNotFoundError:
+ import tomli
+
+ sys.modules["tomllib"] = tomli
+
+
+def _install_datetime_utc() -> None:
+ import datetime as _datetime
+
+ if not hasattr(_datetime, "UTC"):
+ _datetime.UTC = _datetime.timezone.utc
+
+
+def bootstrap() -> None:
+ if sys.version_info >= (3, 9):
+ return
+ _install_datetime_utc()
+ _install_asyncio_to_thread()
+ _install_tomllib()
+
+
+if not getattr(sys, "frozen", False):
+ bootstrap()
diff --git a/meshchatx/src/env_utils.py b/meshchatx/src/env_utils.py
index 4eabe4ba..3a13d9cc 100644
--- a/meshchatx/src/env_utils.py
+++ b/meshchatx/src/env_utils.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Environment variable parsing helpers."""
import os
diff --git a/meshchatx/src/lxst_optional.py b/meshchatx/src/lxst_optional.py
new file mode 100644
index 00000000..fc51de23
--- /dev/null
+++ b/meshchatx/src/lxst_optional.py
@@ -0,0 +1,193 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Install no-op LXST stubs when the real package is unavailable (Python 3.8 builds)."""
+
+from __future__ import annotations
+
+import sys
+import types
+
+
+class _StubCodec:
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def encode(self, samples):
+ return samples
+
+
+class _StubMixer:
+ def can_receive(self, from_source=None):
+ return True
+
+ def handle_frame(self, frame, source=None):
+ pass
+
+
+class _StubPipeline:
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
+
+
+class _StubSource:
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
+
+
+class _StubSink:
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def can_receive(self, from_source=None):
+ return True
+
+ def handle_frame(self, frame, source=None):
+ pass
+
+
+class _StubDestination:
+ hexhash = None
+
+
+class _StubTelephone:
+ def __init__(self, identity):
+ self.identity = identity
+ self.active_call = None
+ self.transmit_mixer = _StubMixer()
+ self.call_status = 3
+ self.busy = False
+ self.destination = _StubDestination()
+ self.target_frame_time_ms = 60
+ self.transmit_codec = _StubCodec()
+
+ def set_busy_tone_time(self, _seconds):
+ pass
+
+ def set_connect_timeout(self, _seconds):
+ pass
+
+ def switch_profile(self, _profile_id, from_signalling=False):
+ pass
+
+ def set_ringing_callback(self, _callback):
+ pass
+
+ def set_established_callback(self, _callback):
+ pass
+
+ def set_ended_callback(self, _callback):
+ pass
+
+ def teardown(self):
+ pass
+
+ def hangup(self):
+ pass
+
+ def call(self, *args, **kwargs):
+ return None
+
+
+class _StubProfiles:
+ DEFAULT_PROFILE = 0
+ QUALITY_MEDIUM = 0
+ QUALITY_HIGH = 1
+ QUALITY_MAX = 2
+
+ @staticmethod
+ def profile_name(profile_id):
+ return "unavailable"
+
+ @staticmethod
+ def available_profiles():
+ return []
+
+ @staticmethod
+ def get_frame_time(profile_id):
+ return 60
+
+
+def _module(name: str, attrs: dict) -> types.ModuleType:
+ module = types.ModuleType(name)
+ for key, value in attrs.items():
+ setattr(module, key, value)
+ return module
+
+
+def _install_lxst_stubs() -> None:
+ codecs = _module(
+ "LXST.Codecs",
+ {
+ "Null": _StubCodec,
+ "Raw": _StubCodec,
+ "Opus": _StubCodec,
+ },
+ )
+ mixer = _module("LXST.Mixer", {"Mixer": _StubMixer})
+ pipeline = _module("LXST.Pipeline", {"Pipeline": _StubPipeline})
+ sinks = _module(
+ "LXST.Sinks",
+ {
+ "LocalSink": _StubSink,
+ "OpusFileSink": _StubSink,
+ },
+ )
+ sources = _module(
+ "LXST.Sources",
+ {
+ "LocalSource": _StubSource,
+ "OpusFileSource": _StubSource,
+ },
+ )
+ telephony = _module("LXST.Primitives.Telephony", {"Profiles": _StubProfiles})
+ primitives = _module("LXST.Primitives", {"Telephony": telephony})
+ lxst = _module(
+ "LXST",
+ {
+ "__version__": "unavailable",
+ "Telephone": _StubTelephone,
+ "ToneSource": _StubSource,
+ "Codecs": codecs,
+ "Mixer": mixer,
+ "Pipeline": pipeline,
+ "Sinks": sinks,
+ "Sources": sources,
+ "Primitives": primitives,
+ },
+ )
+
+ sys.modules["LXST"] = lxst
+ sys.modules["LXST.Codecs"] = codecs
+ sys.modules["LXST.Mixer"] = mixer
+ sys.modules["LXST.Pipeline"] = pipeline
+ sys.modules["LXST.Sinks"] = sinks
+ sys.modules["LXST.Sources"] = sources
+ sys.modules["LXST.Primitives"] = primitives
+ sys.modules["LXST.Primitives.Telephony"] = telephony
+
+
+def lxst_available() -> bool:
+ return ensure_lxst()
+
+
+def ensure_lxst() -> bool:
+ if "LXST" in sys.modules:
+ return getattr(sys.modules["LXST"], "__version__", None) != "unavailable"
+ try:
+ import LXST # noqa: F401
+ except ModuleNotFoundError:
+ _install_lxst_stubs()
+ return False
+ return True
diff --git a/meshchatx/src/path_utils.py b/meshchatx/src/path_utils.py
index 055cc962..963bcf2a 100644
--- a/meshchatx/src/path_utils.py
+++ b/meshchatx/src/path_utils.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Filesystem and HTTP client helpers used at startup and in the web layer."""
import os
diff --git a/meshchatx/src/ssl_self_signed.py b/meshchatx/src/ssl_self_signed.py
index 8a1764cb..fdc5ed97 100644
--- a/meshchatx/src/ssl_self_signed.py
+++ b/meshchatx/src/ssl_self_signed.py
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: 0BSD
+from __future__ import annotations
"""Self-signed TLS certificate generation for local HTTPS."""
import ipaddress
diff --git a/meshchatx/src/version.py b/meshchatx/src/version.py
index 0874c0af..3ddd04b3 100644
--- a/meshchatx/src/version.py
+++ b/meshchatx/src/version.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Version string synced from package.json.
Do not edit by hand. Run: pnpm run version:sync
diff --git a/pyproject.toml b/pyproject.toml
index 80f341cb..6ddf438e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,39 +5,50 @@ description = "A simple mesh network communications app powered by the Reticulum
authors = [
{name = "Quad4"}
]
-license = "0BSD AND MIT"
+license = { text = "0BSD AND MIT" }
readme = "README.md"
-requires-python = ">=3.11"
+requires-python = ">=3.8,<3.12"
keywords = ["reticulum", "meshchat", "meshchatx", "lxmf", "rns"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Other Audience",
"Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
"Topic :: Communications",
"Topic :: Software Development :: Build Tools",
]
dependencies = [
- "aiohttp>=3.14.0",
+ "aiohttp>=3.8.5,<3.9; python_version < '3.9'",
+ "aiohttp>=3.14.0; python_version >= '3.9'",
"idna>=3.15",
"lxmf>=1.0.1",
- "psutil>=7.2.2",
- "bleak==3.0.2",
+ "psutil>=5.9.8,<6; python_version < '3.9'",
+ "psutil>=7.2.2; python_version >= '3.9'",
"pyserial>=3.5",
"rns>=1.3.5",
- "websockets>=16.0",
- "bcrypt>=5.0.0,<6.0.0",
- "aiohttp-session>=2.12.1,<3.0.0",
- "cryptography>=46.0.7,<47.0.0",
+ "websockets>=10.4,<13; python_version < '3.9'",
+ "websockets>=16.0; python_version >= '3.9'",
+ "bcrypt>=4.0.1,<5; python_version < '3.9'",
+ "bcrypt>=5.0.0,<6.0.0; python_version >= '3.9'",
+ "aiohttp-session>=2.11.0,<2.12; python_version < '3.9'",
+ "aiohttp-session>=2.12.1,<3.0.0; python_version >= '3.9'",
+ "cryptography>=41.0.7,<43; python_version < '3.9'",
+ "cryptography>=46.0.7,<47.0.0; python_version >= '3.9'",
"pycparser>=3.0",
"audioop-lts>=0.2.2; python_version >= '3.13'",
"ply>=3.11,<4.0",
- "lxst>=0.4.7",
- "jaraco.context>=6.1.1",
- "miniaudio (>=1.70,<2.0)",
- "cbor2>=6.1.1",
+ "lxst>=0.4.7; python_version >= '3.9'",
+ "jaraco.context>=6.1.1; python_version >= '3.9'",
+ "miniaudio (>=1.70,<2.0); python_version >= '3.9'",
+ "cbor2>=5.4.6,<6; python_version < '3.9'",
+ "cbor2>=6.1.1; python_version >= '3.9'",
+ "tomli>=2.0.1; python_version < '3.11'",
+ "bleak==3.0.2; python_version >= '3.9' and platform_system == 'Windows'",
+ "bleak==0.22.3; python_version < '3.9' and platform_system == 'Windows'",
]
[project.scripts]
diff --git a/requirements-py38-win7.txt b/requirements-py38-win7.txt
new file mode 100644
index 00000000..180d6458
--- /dev/null
+++ b/requirements-py38-win7.txt
@@ -0,0 +1,19 @@
+# Experimental Windows 7 backend-only pins (Python 3.8 x64).
+# Install: py -3.8 -m pip install -r requirements-py38-win7.txt
+# Build frontend on a modern machine first: pnpm run build-frontend
+
+aiohttp>=3.8.5,<3.9
+aiohttp-session>=2.11.0,<2.12
+bcrypt>=4.0.1,<5
+cbor2>=5.4.6,<6
+cryptography>=41.0.7,<43
+idna>=2.10,<4
+lxmf>=1.0.1
+ply>=3.11,<4.0
+psutil>=5.9.8,<6
+pycparser>=2.21
+pyserial>=3.5
+rns>=1.3.5
+tomli>=2.0.1
+websockets>=10.4,<13
+cx-Freeze>=6.15,<7
diff --git a/scripts/legacy/Dockerfile.win7-backend b/scripts/legacy/Dockerfile.win7-backend
new file mode 100644
index 00000000..2f79c427
--- /dev/null
+++ b/scripts/legacy/Dockerfile.win7-backend
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: 0BSD
+# Experimental Windows 7 backend-only cx_Freeze build via Wine (Python 3.8 x64).
+
+FROM debian:bookworm-slim
+
+ENV DEBIAN_FRONTEND=noninteractive \
+ WINEDEBUG=-all \
+ WINEARCH=win64 \
+ WINEPREFIX=/wineprefix \
+ PYTHON_VERSION=3.8.10 \
+ PYTHON_DIR=C:\\Python38
+
+RUN dpkg --add-architecture i386 && \
+ apt-get update && \
+ apt-get install -y --no-install-recommends \
+ ca-certificates \
+ wget \
+ xvfb \
+ wine \
+ wine32:i386 \
+ winbind \
+ unzip && \
+ rm -rf /var/lib/apt/lists/*
+
+WORKDIR /src
+
+COPY scripts/legacy/docker-win7-entry.sh /usr/local/bin/docker-win7-entry.sh
+RUN chmod +x /usr/local/bin/docker-win7-entry.sh
+
+ENTRYPOINT ["/usr/local/bin/docker-win7-entry.sh"]
diff --git a/scripts/legacy/build-win7-backend-docker.sh b/scripts/legacy/build-win7-backend-docker.sh
new file mode 100755
index 00000000..a80e1816
--- /dev/null
+++ b/scripts/legacy/build-win7-backend-docker.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT"
+
+IMAGE="${MESHCHATX_WIN7_IMAGE:-meshchatx-win7-backend:local}"
+DOCKERFILE="scripts/legacy/Dockerfile.win7-backend"
+
+if [[ ! -f meshchatx/public/index.html ]]; then
+ echo "Building frontend assets..."
+ pnpm run build-frontend
+fi
+
+if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
+ CONTAINER_CMD=(docker)
+elif command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then
+ CONTAINER_CMD=(podman)
+elif command -v wine >/dev/null 2>&1; then
+ echo "Docker/Podman unavailable; falling back to host Wine."
+ exec bash scripts/legacy/build-win7-backend-wine.sh
+else
+ echo "No container runtime or Wine found."
+ echo "Install one of:"
+ echo " sudo pacman -S wine wine-mono # then: bash scripts/legacy/build-win7-backend-wine.sh"
+ echo " sudo systemctl start docker # then re-run this script"
+ exit 1
+fi
+
+echo "Building image ${IMAGE} with ${CONTAINER_CMD[0]}..."
+"${CONTAINER_CMD[@]}" build -f "$DOCKERFILE" -t "$IMAGE" .
+
+echo "Running Wine + Python 3.8 cx_Freeze build in container..."
+"${CONTAINER_CMD[@]}" run --rm \
+ -v "$ROOT:/src" \
+ -w /src \
+ "$IMAGE"
+
+echo "Artifact: $ROOT/build/exe/win32-x64/ReticulumMeshChatX.exe"
diff --git a/scripts/legacy/build-win7-backend-wine.sh b/scripts/legacy/build-win7-backend-wine.sh
new file mode 100755
index 00000000..115e545c
--- /dev/null
+++ b/scripts/legacy/build-win7-backend-wine.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=wine-env.sh
+source "$SCRIPT_DIR/wine-env.sh"
+
+export WINEDEBUG="${WINEDEBUG:--all}"
+export WINEARCH="${WINEARCH:-win64}"
+export WINE_WINDOWS_VERSION="${WINE_WINDOWS_VERSION:-win7}"
+export PYTHON_VERSION="${PYTHON_VERSION:-3.8.10}"
+export PYTHON_EXE="python-${PYTHON_VERSION}-amd64.exe"
+export PYTHON_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/${PYTHON_EXE}"
+export PYTHON_WIN="C:/Python38/python.exe"
+
+if ! command -v wine >/dev/null 2>&1; then
+ echo "wine not found. Install with: sudo pacman -S wine wine-mono"
+ exit 1
+fi
+
+wine_run() {
+ if command -v xvfb-run >/dev/null 2>&1; then
+ WINEDEBUG=-all xvfb-run -a wine "$@"
+ else
+ WINEDEBUG=-all wine "$@"
+ fi
+}
+
+ensure_wine_python() {
+ if wine_run "$PYTHON_WIN" -c "import sys; assert sys.version_info[:2] == (3, 8)" 2>/dev/null; then
+ echo "Wine Python 3.8 ready at $PYTHON_WIN (prefix: $WINEPREFIX)"
+ return 0
+ fi
+
+ echo "Initializing Wine prefix at $WINEPREFIX ..."
+ bash scripts/legacy/wine-set-windows-version.sh "$WINE_WINDOWS_VERSION"
+ wine_run wineboot --init
+ sleep 2
+
+ tmp_installer="/tmp/${PYTHON_EXE}"
+ if [[ ! -f "$tmp_installer" ]]; then
+ echo "Downloading ${PYTHON_URL}"
+ wget -q -O "$tmp_installer" "${PYTHON_URL}"
+ fi
+
+ echo "Installing Python ${PYTHON_VERSION} into Wine..."
+ wine_run "$tmp_installer" /quiet InstallAllUsers=1 TargetDir=C:\\Python38 PrependPath=1 Include_test=0
+ wine_run "$PYTHON_WIN" -m ensurepip --upgrade
+ wine_run "$PYTHON_WIN" -m pip install --upgrade pip setuptools wheel
+}
+
+if [[ ! -f meshchatx/public/index.html ]]; then
+ echo "Building frontend assets..."
+ pnpm run build-frontend
+fi
+
+ensure_wine_python
+
+bash scripts/legacy/wine-set-windows-version.sh "$WINE_WINDOWS_VERSION"
+
+echo "Preparing Python 3.8 source compatibility..."
+wine_run "$PYTHON_WIN" scripts/legacy/prepare_py38_sources.py
+
+echo "Installing legacy Python dependencies (this can take several minutes)..."
+wine_run "$PYTHON_WIN" -m pip install -r requirements-py38-win7.txt
+
+export CX_FREEZE_BUILD_EXE="build/exe/win32-x64"
+export CX_FREEZE_TARGET_NAME="ReticulumMeshChatX.exe"
+export PYTHONDONTWRITEBYTECODE=1
+export PLATFORM=win32
+export ARCH=x64
+export PYTHONIOENCODING=utf-8
+export PYTHONUTF8=1
+
+bash "$SCRIPT_DIR/clean-win7-build-output.sh" "$ROOT/build/exe/win32-x64"
+
+echo "Running cx_Freeze via Wine..."
+wine_run "$PYTHON_WIN" cx_setup.py build
+
+if [[ -f build/exe/win32-x64/ReticulumMeshChatX.exe ]]; then
+ if [[ ! -d build/exe/win32-x64/lib/encodings ]] \
+ && ! unzip -l build/exe/win32-x64/lib/library.zip 2>/dev/null | grep -q 'encodings/'; then
+ echo "Build output looks incomplete (stdlib encodings missing)."
+ exit 1
+ fi
+ echo "Build succeeded: $ROOT/build/exe/win32-x64/ReticulumMeshChatX.exe"
+ ls -la build/exe/win32-x64/ReticulumMeshChatX.exe
+else
+ echo "Build finished but ReticulumMeshChatX.exe was not found."
+ exit 1
+fi
diff --git a/scripts/legacy/build-win7-backend.bat b/scripts/legacy/build-win7-backend.bat
new file mode 100644
index 00000000..fe1d0d0d
--- /dev/null
+++ b/scripts/legacy/build-win7-backend.bat
@@ -0,0 +1,43 @@
+@echo off
+setlocal EnableExtensions
+
+set "SCRIPT_DIR=%~dp0"
+set "ROOT_DIR=%SCRIPT_DIR%..\.."
+pushd "%ROOT_DIR%"
+
+where py >nul 2>&1
+if errorlevel 1 (
+ echo Python launcher ^(py^) not found. Install Python 3.8 x64 for Windows.
+ exit /b 1
+)
+
+for /f "delims=" %%V in ('py -3.8 -c "import sys; print(sys.version_info[:2] >= (3, 8))" 2^>nul') do set "HAS38=%%V"
+if not "%HAS38%"=="True" (
+ echo Python 3.8 is required. Install from https://www.python.org/downloads/release/python-3810/
+ exit /b 1
+)
+
+if not exist "meshchatx\public\index.html" (
+ echo Frontend assets missing. Run ^"pnpm run build-frontend^" on a build machine first.
+ exit /b 1
+)
+
+echo Preparing Python 3.8 source compatibility...
+py -3.8 scripts\legacy\prepare_py38_sources.py
+if errorlevel 1 exit /b 1
+
+echo Installing legacy Python dependencies...
+py -3.8 -m pip install -r requirements-py38-win7.txt
+if errorlevel 1 exit /b 1
+
+set "CX_FREEZE_BUILD_EXE=build\exe\win32-x64"
+echo Building cx_Freeze backend...
+py -3.8 cx_setup.py build
+if errorlevel 1 exit /b 1
+
+echo.
+echo Build complete: %ROOT_DIR%\build\exe\win32-x64\ReticulumMeshChatX.exe
+echo Start with: scripts\legacy\start-backend-win7.bat
+
+popd
+endlocal
diff --git a/scripts/legacy/build-win7-backend.sh b/scripts/legacy/build-win7-backend.sh
new file mode 100755
index 00000000..11d627a9
--- /dev/null
+++ b/scripts/legacy/build-win7-backend.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT"
+
+PYTHON_BIN="${PYTHON_BIN:-python3.8}"
+if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
+ echo "Set PYTHON_BIN to a Python 3.8 interpreter (Windows cross-build uses build-win7-backend.bat)."
+ exit 1
+fi
+
+if [[ ! -f meshchatx/public/index.html ]]; then
+ echo "Build frontend assets first: pnpm run build-frontend"
+ exit 1
+fi
+
+"$PYTHON_BIN" scripts/legacy/prepare_py38_sources.py
+"$PYTHON_BIN" -m pip install -r requirements-py38-win7.txt
+export CX_FREEZE_BUILD_EXE="build/exe/win32-x64"
+"$PYTHON_BIN" cx_setup.py build
+
+echo "Backend build: $ROOT/build/exe/win32-x64/ReticulumMeshChatX.exe"
diff --git a/scripts/legacy/clean-win7-build-output.sh b/scripts/legacy/clean-win7-build-output.sh
new file mode 100755
index 00000000..4f51ea96
--- /dev/null
+++ b/scripts/legacy/clean-win7-build-output.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+BUILD_DIR="${1:-$ROOT/build/exe/win32-x64}"
+
+if pkill -f "ReticulumMeshChatX.exe" 2>/dev/null; then
+ echo "Stopped running ReticulumMeshChatX.exe under Wine."
+ sleep 1
+fi
+
+if [[ -d "$BUILD_DIR" ]]; then
+ echo "Removing stale build output: $BUILD_DIR"
+ rm -rf "$BUILD_DIR"
+fi
diff --git a/scripts/legacy/docker-win7-entry.sh b/scripts/legacy/docker-win7-entry.sh
new file mode 100755
index 00000000..59a9d626
--- /dev/null
+++ b/scripts/legacy/docker-win7-entry.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+export WINEDEBUG="${WINEDEBUG:--all}"
+export WINEARCH="${WINEARCH:-win64}"
+export WINEPREFIX="${WINEPREFIX:-/wineprefix}"
+export PYTHON_VERSION="${PYTHON_VERSION:-3.8.10}"
+export PYTHON_EXE="python-${PYTHON_VERSION}-amd64.exe"
+export PYTHON_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/${PYTHON_EXE}"
+export PYTHON_WIN="C:/Python38/python.exe"
+
+wine_run() {
+ xvfb-run -a wine "$@"
+}
+
+ensure_wine_python() {
+ if wine_run "$PYTHON_WIN" -c "import sys; assert sys.version_info[:2] == (3, 8)" 2>/dev/null; then
+ echo "Wine Python 3.8 ready at $PYTHON_WIN (prefix: $WINEPREFIX)"
+ bash /src/scripts/legacy/wine-set-windows-version.sh "${WINE_WINDOWS_VERSION:-win7}"
+ return 0
+ fi
+
+ echo "Initializing Wine prefix..."
+ bash /src/scripts/legacy/wine-set-windows-version.sh "${WINE_WINDOWS_VERSION:-win7}"
+ wine_run wineboot --init
+ sleep 2
+
+ if [[ ! -f "/tmp/${PYTHON_EXE}" ]]; then
+ echo "Downloading ${PYTHON_URL}"
+ wget -q -O "/tmp/${PYTHON_EXE}" "${PYTHON_URL}"
+ fi
+
+ echo "Installing Python ${PYTHON_VERSION} into Wine..."
+ wine_run "/tmp/${PYTHON_EXE}" /quiet InstallAllUsers=1 TargetDir=C:\\Python38 PrependPath=1 Include_test=0
+ wine_run "$PYTHON_WIN" -m ensurepip --upgrade
+ wine_run "$PYTHON_WIN" -m pip install --upgrade pip setuptools wheel
+}
+
+build_backend() {
+ cd /src
+
+ if [[ ! -f meshchatx/public/index.html ]]; then
+ echo "Frontend assets missing. Build with pnpm run build-frontend on the host first."
+ exit 1
+ fi
+
+ echo "Preparing Python 3.8 source compatibility..."
+ wine_run "$PYTHON_WIN" scripts/legacy/prepare_py38_sources.py
+
+ echo "Installing legacy Python dependencies..."
+ wine_run "$PYTHON_WIN" -m pip install -r requirements-py38-win7.txt
+
+ export CX_FREEZE_BUILD_EXE="build/exe/win32-x64"
+ export CX_FREEZE_TARGET_NAME="ReticulumMeshChatX.exe"
+ export PYTHONDONTWRITEBYTECODE=1
+
+ echo "Running cx_Freeze..."
+ wine_run "$PYTHON_WIN" cx_setup.py build
+
+ if [[ -f "build/exe/win32-x64/ReticulumMeshChatX.exe" ]]; then
+ echo "Build succeeded: build/exe/win32-x64/ReticulumMeshChatX.exe"
+ ls -la build/exe/win32-x64/ReticulumMeshChatX.exe
+ else
+ echo "Build finished but ReticulumMeshChatX.exe was not found."
+ exit 1
+ fi
+}
+
+ensure_wine_python
+build_backend
diff --git a/scripts/legacy/prepare_py38_sources.py b/scripts/legacy/prepare_py38_sources.py
new file mode 100644
index 00000000..18cc7a81
--- /dev/null
+++ b/scripts/legacy/prepare_py38_sources.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: 0BSD
+
+"""Add ``from __future__ import annotations`` to MeshChatX Python sources for 3.8."""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+TARGETS = [ROOT / "meshchatx", ROOT / "vendor" / "lxmfy" / "lxmfy"]
+FUTURE_LINE = "from __future__ import annotations\n"
+MARKER = FUTURE_LINE.strip()
+
+
+def should_skip(path: Path) -> bool:
+ return path.name == "compat_py38.py"
+
+
+def insert_future_import(path: Path) -> bool:
+ text = path.read_text(encoding="utf-8")
+ if MARKER in text:
+ return False
+ lines = text.splitlines(keepends=True)
+ insert_at = 0
+ if lines and lines[0].startswith("#!"):
+ insert_at = 1
+ while insert_at < len(lines) and (
+ lines[insert_at].startswith("#") or lines[insert_at].strip() == ""
+ ):
+ insert_at += 1
+ lines.insert(insert_at, FUTURE_LINE)
+ path.write_text("".join(lines), encoding="utf-8")
+ return True
+
+
+def main() -> int:
+ changed = 0
+ for base in TARGETS:
+ if not base.is_dir():
+ continue
+ for path in sorted(base.rglob("*.py")):
+ if should_skip(path):
+ continue
+ if insert_future_import(path):
+ changed += 1
+ print(path.relative_to(ROOT))
+ print(f"Updated {changed} file(s).")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/legacy/rebuild-win7-backend-wine.sh b/scripts/legacy/rebuild-win7-backend-wine.sh
new file mode 100755
index 00000000..6f08cb5f
--- /dev/null
+++ b/scripts/legacy/rebuild-win7-backend-wine.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=wine-env.sh
+source "$SCRIPT_DIR/wine-env.sh"
+
+export WINEDEBUG="${WINEDEBUG:--all}"
+export WINE_WINDOWS_VERSION="${WINE_WINDOWS_VERSION:-win7}"
+export PYTHON_WIN="${PYTHON_WIN:-C:/Python38/python.exe}"
+export CX_FREEZE_BUILD_EXE="build/exe/win32-x64"
+export CX_FREEZE_TARGET_NAME="ReticulumMeshChatX.exe"
+export PYTHONDONTWRITEBYTECODE=1
+export PLATFORM=win32
+export ARCH=x64
+export PYTHONIOENCODING=utf-8
+export PYTHONUTF8=1
+
+if ! command -v wine >/dev/null 2>&1; then
+ echo "wine not found. Install with: sudo pacman -S wine wine-mono"
+ exit 1
+fi
+
+wine_run() {
+ if command -v xvfb-run >/dev/null 2>&1; then
+ WINEDEBUG=-all xvfb-run -a wine "$@"
+ else
+ WINEDEBUG=-all wine "$@"
+ fi
+}
+
+if ! wine_run "$PYTHON_WIN" -c "import sys; assert sys.version_info[:2] == (3, 8)" 2>/dev/null; then
+ echo "Wine Python 3.8 not found. Run: bash scripts/legacy/build-win7-backend-wine.sh"
+ exit 1
+fi
+
+bash "$SCRIPT_DIR/clean-win7-build-output.sh" "$ROOT/build/exe/win32-x64"
+bash "$SCRIPT_DIR/wine-set-windows-version.sh" "$WINE_WINDOWS_VERSION"
+
+echo "Running cx_Freeze via Wine..."
+wine_run "$PYTHON_WIN" cx_setup.py build
+
+if [[ ! -f build/exe/win32-x64/ReticulumMeshChatX.exe ]]; then
+ echo "Build finished but ReticulumMeshChatX.exe was not found."
+ exit 1
+fi
+
+if [[ ! -d build/exe/win32-x64/lib/encodings ]] \
+ && ! unzip -l build/exe/win32-x64/lib/library.zip 2>/dev/null | grep -q 'encodings/'; then
+ echo "Build output looks incomplete (stdlib encodings missing)."
+ echo "Try: bash scripts/legacy/clean-win7-build-output.sh && bash scripts/legacy/rebuild-win7-backend-wine.sh"
+ exit 1
+fi
+
+echo "Build succeeded: $ROOT/build/exe/win32-x64/ReticulumMeshChatX.exe"
+ls -la build/exe/win32-x64/ReticulumMeshChatX.exe
diff --git a/scripts/legacy/run-backend-wine.sh b/scripts/legacy/run-backend-wine.sh
new file mode 100755
index 00000000..30af7816
--- /dev/null
+++ b/scripts/legacy/run-backend-wine.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=wine-env.sh
+source "$SCRIPT_DIR/wine-env.sh"
+
+export WINE_WINDOWS_VERSION="${WINE_WINDOWS_VERSION:-win7}"
+
+_resolve_backend_exe() {
+ local candidates=(
+ "$ROOT/build/exe/win32-x64/ReticulumMeshChatX.exe"
+ "$ROOT/build/exe/ReticulumMeshChatX.exe"
+ )
+ local candidate
+ for candidate in "${candidates[@]}"; do
+ if [[ -f "$candidate" ]]; then
+ printf '%s\n' "$candidate"
+ return 0
+ fi
+ done
+ return 1
+}
+
+EXE="$(_resolve_backend_exe || true)"
+if [[ -z "$EXE" ]]; then
+ echo "Missing ReticulumMeshChatX.exe — build first:"
+ echo " bash scripts/legacy/build-win7-backend-wine.sh"
+ echo "or (quick rebuild from repo root):"
+ echo " export CX_FREEZE_BUILD_EXE=build/exe/win32-x64"
+ echo " wine C:/Python38/python.exe cx_setup.py build"
+ exit 1
+fi
+
+bash "$SCRIPT_DIR/wine-set-windows-version.sh" "$WINE_WINDOWS_VERSION"
+
+if command -v xvfb-run >/dev/null 2>&1; then
+ WINEDEBUG=-all xvfb-run -a wine "$EXE" "$@"
+else
+ WINEDEBUG=-all wine "$EXE" "$@"
+fi
diff --git a/scripts/legacy/start-backend-win7.bat b/scripts/legacy/start-backend-win7.bat
new file mode 100644
index 00000000..20cd6a9e
--- /dev/null
+++ b/scripts/legacy/start-backend-win7.bat
@@ -0,0 +1,31 @@
+@echo off
+setlocal EnableExtensions
+
+set "SCRIPT_DIR=%~dp0"
+set "ROOT_DIR=%SCRIPT_DIR%..\.."
+set "BACKEND_EXE=%ROOT_DIR%\build\exe\win32-x64\ReticulumMeshChatX.exe"
+set "STORAGE_DIR=%USERPROFILE%\.reticulum-meshchatx"
+set "CONFIG_DIR=%USERPROFILE%\.reticulum"
+set "SUPERMIUM=%ProgramFiles%\Supermium\chrome.exe"
+set "URL=https://127.0.0.1:9337"
+
+if not exist "%BACKEND_EXE%" (
+ echo Backend executable not found:
+ echo %BACKEND_EXE%
+ echo Build it first with scripts\legacy\build-win7-backend.bat
+ exit /b 1
+)
+
+start "" /B "%BACKEND_EXE%" --headless --port 9337 --storage-dir "%STORAGE_DIR%" --reticulum-config-dir "%CONFIG_DIR%"
+
+echo Waiting for backend on %URL% ...
+timeout /t 5 /nobreak >nul
+
+if exist "%SUPERMIUM%" (
+ start "" "%SUPERMIUM%" --new-window "%URL%"
+) else (
+ echo Supermium not found at %SUPERMIUM%
+ echo Open %URL% in a Chromium 111+ browser manually.
+)
+
+endlocal
diff --git a/scripts/legacy/wine-env.sh b/scripts/legacy/wine-env.sh
new file mode 100644
index 00000000..d553f465
--- /dev/null
+++ b/scripts/legacy/wine-env.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: 0BSD
+
+# Normalize WINEPREFIX to an absolute path (Wine rejects "~" literally).
+
+_legacy_wine_expand_prefix() {
+ local p="${1:-$HOME/.wine-meshchatx-py38}"
+ case "$p" in
+ "~") p="$HOME" ;;
+ "~/"*) p="$HOME/${p#~/}" ;;
+ esac
+ if command -v realpath >/dev/null 2>&1; then
+ realpath -m "$p"
+ return
+ fi
+ if command -v readlink >/dev/null 2>&1; then
+ readlink -f "$p" 2>/dev/null && return
+ fi
+ (
+ cd "$(dirname "$p")" 2>/dev/null || exit 1
+ printf '%s/%s\n' "$(pwd)" "$(basename "$p")"
+ )
+}
+
+export WINEPREFIX="$(_legacy_wine_expand_prefix "${WINEPREFIX:-$HOME/.wine-meshchatx-py38}")"
diff --git a/scripts/legacy/wine-set-windows-version.sh b/scripts/legacy/wine-set-windows-version.sh
new file mode 100755
index 00000000..da3b9213
--- /dev/null
+++ b/scripts/legacy/wine-set-windows-version.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: 0BSD
+# Set emulated Windows version for a Wine prefix (win7, winxp, win10, etc.).
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=wine-env.sh
+source "$SCRIPT_DIR/wine-env.sh"
+
+VERSION="${1:-win7}"
+
+case "$VERSION" in
+ win11|win10|win81|win8|win7|vista|win2008|win2003|winxp|win98|win95)
+ ;;
+ *)
+ echo "Unsupported Wine version token: $VERSION"
+ echo "Examples: win7, winxp, win10"
+ exit 1
+ ;;
+esac
+
+mkdir -p "$WINEPREFIX"
+USER_REG="$WINEPREFIX/user.reg"
+
+if [[ -f "$USER_REG" ]] && grep -q '^"Version"=' "$USER_REG" 2>/dev/null; then
+ sed -i "s/^\"Version\"=.*/\"Version\"=\"$VERSION\"/" "$USER_REG"
+else
+ cat >>"$USER_REG" <<EOF
+
+[Software\\Wine]
+"Version"="$VERSION"
+EOF
+fi
+
+echo "Wine prefix $WINEPREFIX now emulates Windows: $VERSION"
diff --git a/vendor/lxmfy/lxmfy/__init__.py b/vendor/lxmfy/lxmfy/__init__.py
index 391cc5e0..8e7b2453 100644
--- a/vendor/lxmfy/lxmfy/__init__.py
+++ b/vendor/lxmfy/lxmfy/__init__.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""LXMFy - A bot framework for creating LXMF bots on the Reticulum Network.
This package provides tools and utilities for creating and managing LXMF bots,
diff --git a/vendor/lxmfy/lxmfy/__version__.py b/vendor/lxmfy/lxmfy/__version__.py
index a3685971..e41fcf17 100644
--- a/vendor/lxmfy/lxmfy/__version__.py
+++ b/vendor/lxmfy/lxmfy/__version__.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
import tomllib
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
diff --git a/vendor/lxmfy/lxmfy/attachments.py b/vendor/lxmfy/lxmfy/attachments.py
index e481d0f7..518987db 100644
--- a/vendor/lxmfy/lxmfy/attachments.py
+++ b/vendor/lxmfy/lxmfy/attachments.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
from dataclasses import dataclass
from enum import IntEnum
diff --git a/vendor/lxmfy/lxmfy/cli.py b/vendor/lxmfy/lxmfy/cli.py
index bd3cd121..074db6ca 100644
--- a/vendor/lxmfy/lxmfy/cli.py
+++ b/vendor/lxmfy/lxmfy/cli.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""CLI module for LXMFy bot framework.
Provides an interactive and colorful command-line interface for creating and managing LXMF bots,
diff --git a/vendor/lxmfy/lxmfy/cogs_core.py b/vendor/lxmfy/lxmfy/cogs_core.py
index ee57ef8c..21b91686 100644
--- a/vendor/lxmfy/lxmfy/cogs_core.py
+++ b/vendor/lxmfy/lxmfy/cogs_core.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Cogs management module for LXMFy.
This module provides functionality for loading and managing cogs (extension modules)
diff --git a/vendor/lxmfy/lxmfy/colors.py b/vendor/lxmfy/lxmfy/colors.py
index 2105a955..4a6e0992 100644
--- a/vendor/lxmfy/lxmfy/colors.py
+++ b/vendor/lxmfy/lxmfy/colors.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Color support module for LXMFy CLI.
Provides cross-platform color support for terminal output.
diff --git a/vendor/lxmfy/lxmfy/commands.py b/vendor/lxmfy/lxmfy/commands.py
index cce920d3..e8a99e22 100644
--- a/vendor/lxmfy/lxmfy/commands.py
+++ b/vendor/lxmfy/lxmfy/commands.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Command handling module for LXMFy.
This module provides the core command handling functionality for LXMFy bots,
diff --git a/vendor/lxmfy/lxmfy/config.py b/vendor/lxmfy/lxmfy/config.py
index 9442ffd2..57eb3498 100644
--- a/vendor/lxmfy/lxmfy/config.py
+++ b/vendor/lxmfy/lxmfy/config.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Configuration module for LXMFy."""
import os
diff --git a/vendor/lxmfy/lxmfy/core.py b/vendor/lxmfy/lxmfy/core.py
index 3a12ac06..8e784dd3 100644
--- a/vendor/lxmfy/lxmfy/core.py
+++ b/vendor/lxmfy/lxmfy/core.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Core module for LXMFy bot framework.
This module provides the main LXMFBot class that handles message routing,
diff --git a/vendor/lxmfy/lxmfy/events.py b/vendor/lxmfy/lxmfy/events.py
index 8572d795..bb5efd2f 100644
--- a/vendor/lxmfy/lxmfy/events.py
+++ b/vendor/lxmfy/lxmfy/events.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Event system module for LXMFy.
This module provides a comprehensive event handling system including:
diff --git a/vendor/lxmfy/lxmfy/help.py b/vendor/lxmfy/lxmfy/help.py
index 4814afb9..a066fa5e 100644
--- a/vendor/lxmfy/lxmfy/help.py
+++ b/vendor/lxmfy/lxmfy/help.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Help command system for LXMFy."""
from dataclasses import dataclass
diff --git a/vendor/lxmfy/lxmfy/lxmf_fields.py b/vendor/lxmfy/lxmfy/lxmf_fields.py
index 4aefd872..c6b6eec3 100644
--- a/vendor/lxmfy/lxmfy/lxmf_fields.py
+++ b/vendor/lxmfy/lxmfy/lxmf_fields.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""LXMF field constants and helpers for structured commands and results."""
try:
diff --git a/vendor/lxmfy/lxmfy/middleware.py b/vendor/lxmfy/lxmfy/middleware.py
index ba0133c2..c37a2d85 100644
--- a/vendor/lxmfy/lxmfy/middleware.py
+++ b/vendor/lxmfy/lxmfy/middleware.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Middleware system for LXMFy.
This module provides a flexible middleware system for processing messages
diff --git a/vendor/lxmfy/lxmfy/moderation.py b/vendor/lxmfy/lxmfy/moderation.py
index 3cee7f83..7f8fd05f 100644
--- a/vendor/lxmfy/lxmfy/moderation.py
+++ b/vendor/lxmfy/lxmfy/moderation.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Spam protection module for LXMFy.
This module provides spam protection functionality for LXMFy bots,
diff --git a/vendor/lxmfy/lxmfy/nlp.py b/vendor/lxmfy/lxmfy/nlp.py
index 964e980a..7fc5d1f9 100644
--- a/vendor/lxmfy/lxmfy/nlp.py
+++ b/vendor/lxmfy/lxmfy/nlp.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""NLP module for LXMFy.
This module provides lightweight, local intent classification using mathematical
diff --git a/vendor/lxmfy/lxmfy/permissions.py b/vendor/lxmfy/lxmfy/permissions.py
index 7ca71a10..bee49467 100644
--- a/vendor/lxmfy/lxmfy/permissions.py
+++ b/vendor/lxmfy/lxmfy/permissions.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Permissions system for LXMFy."""
from dataclasses import dataclass, field
diff --git a/vendor/lxmfy/lxmfy/scheduler.py b/vendor/lxmfy/lxmfy/scheduler.py
index 493fcbc1..9bd7fde3 100644
--- a/vendor/lxmfy/lxmfy/scheduler.py
+++ b/vendor/lxmfy/lxmfy/scheduler.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Task scheduling system for LXMFy.
This module provides cron-style scheduling and background task management
diff --git a/vendor/lxmfy/lxmfy/signatures.py b/vendor/lxmfy/lxmfy/signatures.py
index 1f822761..9f29ff11 100644
--- a/vendor/lxmfy/lxmfy/signatures.py
+++ b/vendor/lxmfy/lxmfy/signatures.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Signature management module for LXMFy.
This module provides cryptographic signing and verification capabilities
diff --git a/vendor/lxmfy/lxmfy/storage.py b/vendor/lxmfy/lxmfy/storage.py
index c1a0295f..80fb9866 100644
--- a/vendor/lxmfy/lxmfy/storage.py
+++ b/vendor/lxmfy/lxmfy/storage.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Storage module for LXMFy bot framework.
This module provides abstract and concrete storage implementations for persistent data storage.
diff --git a/vendor/lxmfy/lxmfy/templates/__init__.py b/vendor/lxmfy/lxmfy/templates/__init__.py
index 0be030ed..0bce5c62 100644
--- a/vendor/lxmfy/lxmfy/templates/__init__.py
+++ b/vendor/lxmfy/lxmfy/templates/__init__.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Templates module for LXMFy bot framework.
This module provides ready-to-use bot templates with different feature sets.
diff --git a/vendor/lxmfy/lxmfy/templates/cog_test_bot.py b/vendor/lxmfy/lxmfy/templates/cog_test_bot.py
index 8ac52cdd..34a3cf26 100644
--- a/vendor/lxmfy/lxmfy/templates/cog_test_bot.py
+++ b/vendor/lxmfy/lxmfy/templates/cog_test_bot.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""CogTest Bot Template - Tests cog command loading functionality.
This template demonstrates proper cog usage and serves as a test case
diff --git a/vendor/lxmfy/lxmfy/templates/echo_bot.py b/vendor/lxmfy/lxmfy/templates/echo_bot.py
index 3445b244..ae8fd508 100644
--- a/vendor/lxmfy/lxmfy/templates/echo_bot.py
+++ b/vendor/lxmfy/lxmfy/templates/echo_bot.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Simple echo bot template with cryptographic signature verification."""
from lxmfy import IconAppearance, LXMFBot, pack_icon_appearance_field
diff --git a/vendor/lxmfy/lxmfy/templates/note_bot.py b/vendor/lxmfy/lxmfy/templates/note_bot.py
index 6a8f2fda..92a3915c 100644
--- a/vendor/lxmfy/lxmfy/templates/note_bot.py
+++ b/vendor/lxmfy/lxmfy/templates/note_bot.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Note-taking bot with JSON storage."""
from datetime import datetime
diff --git a/vendor/lxmfy/lxmfy/templates/reminder_bot.py b/vendor/lxmfy/lxmfy/templates/reminder_bot.py
index 920d5b18..1971a1db 100644
--- a/vendor/lxmfy/lxmfy/templates/reminder_bot.py
+++ b/vendor/lxmfy/lxmfy/templates/reminder_bot.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Reminder bot with SQLite storage."""
import re
diff --git a/vendor/lxmfy/lxmfy/transport.py b/vendor/lxmfy/lxmfy/transport.py
index bf3aa7b8..f3154492 100644
--- a/vendor/lxmfy/lxmfy/transport.py
+++ b/vendor/lxmfy/lxmfy/transport.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Transport module for LXMFy bot framework.
This module provides transport layer functionality for establishing and managing
diff --git a/vendor/lxmfy/lxmfy/validation.py b/vendor/lxmfy/lxmfy/validation.py
index 0e99229a..b040674b 100644
--- a/vendor/lxmfy/lxmfy/validation.py
+++ b/vendor/lxmfy/lxmfy/validation.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
"""Validation module for LXMFy configuration and best practices."""
import logging
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────